<?php

namespace Yonyou\Contracts;

use Yonyou\Exceptions\InvalidArgumentException;
use Yonyou\Exceptions\InvalidResponseException;
use Yonyou\Exceptions\LocalCacheException;

class BaseU8
{
    /**
     * 当前公共参数配置
     *
     * @var DataArray
     */
    public $config;

    /**
     * 访问AccessToken
     *
     * @var string
     */
    public $token = '';

    /**
     * 当前请求方法参数
     *
     * @var array
     */
    protected $currentMethod = [];

    /**
     * 当前模式
     *
     * @var bool
     */
    protected $isTry = false;

    /**
     * 静态缓存
     *
     * @var static
     */
    protected static $cache;

    /**
     * 注册代替函数
     *
     * @var string
     */
    protected $getTokenCallback;

    /**
     * constructor.
     *
     * @param array $options
     */
    public function __construct($options)
    {
        if (empty($options['from_account'])) {
            throw new InvalidArgumentException("Missing Config -- [from_account]");
        }
        if (empty($options['app_key'])) {
            throw new InvalidArgumentException("Missing Config -- [app_key]");
        }
        if (empty($options['app_secret'])) {
            throw new InvalidArgumentException("Missing Config -- [app_secret]");
        }
        if (isset($options['getTokenCallback']) && is_callable($options['getTokenCallback'])) {
            $this->getTokenCallback = $options['getTokenCallback'];
        }
        if (!empty($options['cache_path'])) {
            Tools::$cache_path = $options['cache_path'];
        }
        $this->config = new DataArray($options);
    }

    /**
     * 静态创建对象
     *
     * @param array $config
     * @return static
     */
    public static function instance($config)
    {
        $key = md5(get_called_class() . serialize($config));
        if (isset(self::$cache[$key])) {
            return self::$cache[$key];
        }
        return self::$cache[$key] = new static($config);
    }

    /**
     * 获取访问 AccessToken
     *
     * @return string
     * @throws LocalCacheException
     * @throws InvalidResponseException
     */
    public function getToken()
    {
        if (!empty($this->token)) {
            return $this->token;
        }
        $cache = $this->config->get('app_key') . '_token';
        $this->token = Tools::getCache($cache);
        if (!empty($this->token)) {
            return $this->token;
        }
        // 处理开放平台授权公众号获取AccessToken
        if (!empty($this->getTokenCallback) && is_callable($this->getTokenCallback)) {
            $this->token = call_user_func_array($this->getTokenCallback, [$this->config->get('app_key'), $this]);
            if (!empty($this->token)) {
                Tools::setCache($cache, $this->token, 7000);
            }
            return $this->token;
        }
        list($fromAccount, $appKey, $appSecret) = [$this->config->get('from_account'), $this->config->get('app_key'), $this->config->get('app_secret')];
        $url = "https://api.yonyouup.com/system/token?from_account={$fromAccount}&app_key={$appKey}&app_secret={$appSecret}";
        $result = Tools::json2arr(Tools::get($url));
        if (!empty($result['token'])) {
            Tools::setCache($cache, $result['token']['id'], 7000);
        }
        return $this->token = $result['token']['id'];
    }

    /**
     * @param $token
     * @throws LocalCacheException
     */
    public function setToken($token)
    {
        if (!is_string($token)) {
            throw new InvalidArgumentException("Invalid Token type, need string.");
        }
        $cache = $this->config->get('app_key') . '_token';
        Tools::setCache($cache, $this->token = $token);
    }

    /**
     * 清理删除 AccessToken
     *
     * @return bool
     */
    public function delToken()
    {
        $this->token = '';
        return Tools::delCache($this->config->get('app_key') . '_token');
    }

    /**
     * 以GET获取接口数据并转为数组
     *
     * @param string $url 接口地址
     * @return array
     * @throws InvalidResponseException
     */
    protected function httpGetForJson($url)
    {
        try {
            return Tools::json2arr(Tools::get($url));
        } catch (InvalidResponseException $exception) {
            if (isset($this->currentMethod['method']) && empty($this->isTry)) {
                //token相关的错误码
                if (in_array($exception->getCode(), ['10004', '30006', '30010', '30011', '30012'])) {
                    [$this->delToken(), $this->isTry = true];
                    return call_user_func_array([$this, $this->currentMethod['method']], $this->currentMethod['arguments']);
                }
            }
            throw new InvalidResponseException($exception->getMessage(), $exception->getCode());
        }
    }

    /**
     * 以POST获取接口数据并转为数组
     *
     * @param string $url  接口地址
     * @param array  $data 请求数据
     * @param bool   $buildToJson
     * @return array
     * @throws InvalidResponseException
     */
    protected function httpPostForJson($url, $data, $buildToJson = true)
    {
        try {
            $options = [];
            if ($buildToJson) {
                $options['headers'] = ['Content-Type: application/json'];
            }
            return Tools::json2arr(Tools::post($url, $buildToJson ? Tools::arr2json($data) : $data, $options));
        } catch (InvalidResponseException $exception) {
            //token相关的错误码
            if (!$this->isTry && in_array($exception->getCode(), ['10004', '30006', '30010', '30011', '30012'])) {
                [$this->delToken(), $this->isTry = true];
                return call_user_func_array([$this, $this->currentMethod['method']], $this->currentMethod['arguments']);
            }
            throw new InvalidResponseException($exception->getMessage(), $exception->getCode());
        }
    }

    /**
     * 注册当前请求接口
     *
     * @param string $url       接口地址
     * @param string $method    当前接口方法
     * @param array  $arguments 请求参数
     * @return string
     * @throws InvalidResponseException
     * @throws LocalCacheException
     */
    protected function registerApi(&$url, $method, $arguments = [])
    {
        $this->currentMethod = ['method' => $method, 'arguments' => $arguments];
        if (empty($this->token)) {
            $this->token = $this->getToken();
        }
        return $url = str_replace('TOKEN', urlencode($this->token), $url);
    }

    /**
     * 接口通用POST请求方法
     *
     * @param string $url  接口URL
     * @param array  $data POST提交接口参数
     * @param bool   $isBuildJson
     * @return array
     * @throws InvalidResponseException
     * @throws LocalCacheException
     */
    public function callPostApi($url, $data, $isBuildJson = true)
    {
        $this->registerApi($url, __FUNCTION__, func_get_args());
        return $this->httpPostForJson($url, $data, $isBuildJson);
    }

    /**
     * 接口通用GET请求方法
     *
     * @param string $url 接口URL
     * @return array
     * @throws InvalidResponseException
     * @throws LocalCacheException
     */
    public function callGetApi($url)
    {
        $this->registerApi($url, __FUNCTION__, func_get_args());
        return $this->httpGetForJson($url);
    }
}
